Skip to content

test: stop the paired-mousemove test racing a 16ms wall-clock window - #90

Merged
ralyodio merged 1 commit into
masterfrom
fix/pointerlock-test-flake
Aug 29, 2026
Merged

test: stop the paired-mousemove test racing a 16ms wall-clock window#90
ralyodio merged 1 commit into
masterfrom
fix/pointerlock-test-flake

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

usePointerLock drops the mousemove that Chromium pairs with a pointermove by checking a wall clock:

const onMouseMove = (event: MouseEvent) => {
  if (performance.now() - lastPointerMoveAt < 16) return;
  move(event);
};

The test dispatches that pair on consecutive lines and asserts the second is ignored — so real elapsed time is part of the assertion. On a loaded CI runner more than 16ms can pass between those two lines, the mousemove is taken as a second movement, and the virtual pointer reads 0.7 instead of 0.6.

That is what turned #87 red. It changes only apps/desktop/src/main/**, yet failed in apps/web, while the byte-identical desktop copy of the same test passed in the same run. A plain re-run went green, which is the signature of a flake rather than a break.

The fix

Freeze performance.now() across the paired dispatch, in both copies of the test. Scoped to just those two dispatchEvent calls and restored in a finally, so everything else in the test still runs on real time.

Nothing that ships changes. The 16ms window is still the shipped behaviour — it simply is no longer a race in the test.

Confirming the diagnosis

Rather than assume, I re-ran the same test with a clock stepping 20ms per call (just past the window) and it reproduced CI's diff exactly:

-     "x": 0.6,     +     "x": 0.7,
-     "y": 0.6,     +     "y": 0.7,

Same two values, same direction. With the clock frozen it passes.

Checks

The pre-commit hook was bypassed — its parallel turbo fan-out OOMs on this machine. The gates were run individually instead, all green:

  • npx vitest run in apps/web — 864 tests across 82 files
  • npx vitest run in apps/desktop — 609 tests across 58 files
  • typecheck on both apps — clean
  • lint on both apps — clean (2 pre-existing warnings in apps/web, untouched here)
  • prettier --check on both changed files

🤖 Generated with Claude Code

https://claude.ai/code/session_013FGWJHRL6B6ExLg25UenBS

`usePointerLock` suppresses the mousemove that Chromium pairs with a
pointermove by checking `performance.now() - lastPointerMoveAt < 16`. The test
dispatches the pair on consecutive lines and expects the second to be dropped.

That makes real elapsed time part of the assertion. On a loaded CI runner more
than 16ms can pass between those two dispatches, the mousemove is taken as a
second movement, and the pointer reads 0.7 instead of 0.6 -- a red build with
nothing wrong in the code. It failed exactly this way on PR #87, which touches
only apps/desktop, while the identical desktop copy of the test passed in the
same run.

Freezes performance.now across the paired dispatch in both copies. Confirmed by
running the same test with a clock stepping 20ms per call, which reproduces the
CI diff exactly (0.7 vs 0.6). Nothing that ships changes -- the 16ms window is
still the shipped behaviour, it just is not a race in the test any more.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FGWJHRL6B6ExLg25UenBS
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

72 finding(s)

HIGH/CRITICAL: 13 | MEDIUM: 41 | LOW: 18

Severity Rule Location
HIGH sh-eval-expansion .githooks/pre-commit:33
HIGH js-electron-node-integration apps/desktop/src/main/window.ts:49
HIGH sh-remote-script-execution apps/installer/scripts/install.sh:691
HIGH sh-unquoted-expansion-destructive apps/installer/scripts/install.sh:715
HIGH sh-remote-script-execution apps/installer/scripts/install.sh:813
HIGH sh-remote-script-execution apps/installer/scripts/install.sh:815
HIGH sh-remote-script-execution apps/installer/scripts/install.sh:894
HIGH sh-unquoted-expansion-destructive apps/installer/scripts/install.sh:910
HIGH sh-remote-script-execution apps/installer/scripts/install.sh:1064
HIGH sh-remote-script-execution apps/installer/scripts/install.sh:1066
HIGH sh-remote-script-execution apps/installer/scripts/install.sh:1128
HIGH sh-remote-script-execution apps/livekit/setup-livekit-server.sh:93
HIGH sh-remote-script-execution apps/turn/deploy-droplet.sh:62
MEDIUM insecure-temp-file .githooks/commit-msg:19
MEDIUM insecure-temp-file .githooks/post-commit:22
MEDIUM insecure-temp-file apps/installer/scripts/install.sh:370
MEDIUM insecure-temp-file apps/installer/scripts/install.sh:393
MEDIUM insecure-temp-file apps/installer/scripts/install.sh:402
MEDIUM insecure-temp-file apps/installer/scripts/install.sh:438
MEDIUM js-unescaped-html-sink apps/web/src/app/blog/[slug]/page.tsx:48
MEDIUM js-unescaped-html-sink apps/web/src/app/blog/[slug]/page.tsx:73
MEDIUM js-unescaped-html-sink apps/web/src/app/c/[handle]/page.tsx:192
MEDIUM js-unescaped-html-sink apps/web/src/app/l/[joinCode]/page.tsx:129
MEDIUM js-unescaped-html-sink apps/web/src/app/l/[joinCode]/page.tsx:213
MEDIUM js-unescaped-html-sink apps/web/src/app/layout.tsx:142
MEDIUM js-unescaped-html-sink apps/web/src/app/live/page.tsx:145
MEDIUM js-unescaped-html-sink apps/web/src/app/page.tsx:122
MEDIUM js-unescaped-html-sink apps/web/src/app/pricing/page.tsx:284
MEDIUM js-open-redirect apps/web/src/app/pricing/UpgradeButton.tsx:50
MEDIUM js-unescaped-html-sink apps/web/src/app/u/[username]/page.tsx:282
MEDIUM js-open-redirect apps/web/src/hooks/useDesktopHandoff.ts:24
MEDIUM redos-nested-quantifier apps/web/src/lib/deliverable.ts:11
MEDIUM js-unescaped-html-sink apps/web/src/lib/player/player.ts:124
MEDIUM js-unescaped-html-sink apps/web/src/lib/player/player.ts:393
MEDIUM js-unescaped-html-sink apps/web/src/lib/player/player.ts:396
MEDIUM js-unescaped-html-sink apps/web/src/lib/player/player.ts:405
MEDIUM js-unescaped-html-sink apps/web/src/lib/player/player.ts:608
MEDIUM manifest-install-lifecycle-script package.json:55
MEDIUM sql-template-interpolation packages/ai-core/src/prompts.ts:36
MEDIUM js-shell-exec-interpolation scripts/lib/package-managers/apt.ts:154
MEDIUM js-shell-exec-interpolation scripts/lib/package-managers/apt.ts:160
MEDIUM js-shell-exec-interpolation scripts/lib/package-managers/apt.ts:208
MEDIUM js-shell-exec-interpolation scripts/lib/package-managers/apt.ts:313
MEDIUM js-shell-exec-interpolation scripts/lib/package-managers/aur.ts:342
MEDIUM js-shell-exec-interpolation scripts/lib/package-managers/chocolatey.ts:264
MEDIUM js-shell-exec-interpolation scripts/lib/package-managers/chocolatey.ts:288
MEDIUM js-shell-exec-interpolation scripts/lib/package-managers/rpm.ts:201
MEDIUM js-shell-exec-interpolation scripts/lib/package-managers/rpm.ts:261
MEDIUM js-shell-exec-interpolation scripts/release.mjs:145
MEDIUM js-shell-exec-interpolation scripts/release.mjs:146

…and 22 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 07d1d3f into master Aug 29, 2026
13 checks passed
@ralyodio
ralyodio deleted the fix/pointerlock-test-flake branch August 29, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant